package web.suzy.oj.manager.oj;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Validator;
import cn.hutool.crypto.SecureUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import org.apache.shiro.SecurityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import web.suzy.oj.common.exception.StatusFailException;
import web.suzy.oj.common.exception.StatusSystemErrorException;
import web.suzy.oj.dao.problem.ProblemEntityService;
import web.suzy.oj.dao.user.*;
import web.suzy.oj.pojo.dto.ChangeEmailDTO;
import web.suzy.oj.pojo.dto.ChangePasswordDTO;
import web.suzy.oj.pojo.dto.CheckUsernameOrEmailDTO;
import web.suzy.oj.pojo.entity.judge.Judge;
import web.suzy.oj.pojo.entity.problem.Problem;
import web.suzy.oj.pojo.entity.user.Session;
import web.suzy.oj.pojo.entity.user.SysRole;
import web.suzy.oj.pojo.entity.user.UserAcproblem;
import web.suzy.oj.pojo.entity.user.UserInfo;
import web.suzy.oj.pojo.vo.*;
import web.suzy.oj.shiro.AccountProfile;
import web.suzy.oj.utils.Constants;
import web.suzy.oj.utils.RedisUtils;
import web.suzy.oj.validator.CommonValidator;

import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;

/**
 * YangSuzy 软件工作室
 * 类名: AccountManager
 * 描述: ---- 待定 -----
 * 功能: 负责处理账号的相关操作
 *
 * @author YangSuzy
 * Date: 2022/11/14 17:47
 */
@Component
public class AccountManager {
    @Autowired
    private RedisUtils redisUtils;

    @Autowired
    private UserInfoEntityService userInfoEntityService;

    @Autowired
    private SysUserRoleEntityService userRoleEntityService;

    @Autowired
    private UserRecordEntityService userRecordEntityService;

    @Autowired
    private UserAcproblemEntityService userAcproblemEntityService;

    @Autowired
    private ProblemEntityService problemEntityService;

    @Autowired
    private SessionEntityService sessionEntityService;

    @Autowired
    private CommonValidator commonValidator;

    /**
     * 方法名: checkUsernameOrEmail
     * 描述: 检查用户或邮箱是否存在
     *
     * @param checkUsernameOrEmailDto 数据传输对象
     * @return web.suzy.oj.pojo.vo.CheckUsernameOrEmailVO
     * @date 2022/11/14 17:51
     * @auther YangSuzy
     **/
    public CheckUsernameOrEmailVO checkUsernameOrEmail(CheckUsernameOrEmailDTO checkUsernameOrEmailDto) {
        //获取用户名和邮箱
        String email = checkUsernameOrEmailDto.getEmail();
        String username = checkUsernameOrEmailDto.getUsername();
        //标记位
        boolean rightEmail = false;
        boolean rightUsername = false;

        //判断邮箱是否为空
        if (!StringUtils.isEmpty(email)) {
            email = email.trim();
            //判断邮箱格式是否正确
            boolean isEmail = Validator.isEmail(email);
            if (isEmail) {
                //查询是否有用户使用该邮箱
                QueryWrapper<UserInfo> wrapper = new QueryWrapper<UserInfo>().eq("email", email);
                UserInfo user = userInfoEntityService.getOne(wrapper, false);
                rightEmail = user != null;
            }
        }

        //判断用户名是否为空
        if (!StringUtils.isEmpty(username)) {
            username = username.trim();
            //查询是否由用户使用该用户名
            QueryWrapper<UserInfo> wrapper = new QueryWrapper<UserInfo>().eq("username", username);
            UserInfo user = userInfoEntityService.getOne(wrapper, false);
            rightUsername = user != null;
        }

        //根据标识符设定检查结果
        CheckUsernameOrEmailVO checkUsernameOrEmailVo = new CheckUsernameOrEmailVO();
        checkUsernameOrEmailVo.setEmail(rightEmail);
        checkUsernameOrEmailVo.setUsername(rightUsername);
        return checkUsernameOrEmailVo;
    }

    /**
     * 方法名: changePassword
     * 描述: 修改密码，若 30分钟内连续错误5次，则锁定功能 30分钟后才能操作
     *
     * @param changePasswordDto
     * @return ChangeAccountVO
     * @date 2022/11/14 18:14
     * @auther YangSuzy
     **/
    public ChangeAccountVO changePassword(ChangePasswordDTO changePasswordDto) throws StatusSystemErrorException, StatusFailException {
        //获取前端输入的数据
        String oldPassword = changePasswordDto.getOldPassword();
        String newPassword = changePasswordDto.getNewPassword();
        //数据可用性判断
        if (StringUtils.isEmpty(oldPassword) || StringUtils.isEmpty(newPassword)) {
            throw new StatusFailException("错误：原始密码或新密码不能为空！");
        }
        if (newPassword.length() < 6 || newPassword.length() > 20) {
            throw new StatusFailException("新密码长度应该为6~20位！");
        }
        //获取当前登录的用户
        AccountProfile userRolesVo = (AccountProfile) SecurityUtils.getSubject().getPrincipal();

        //修改锁Key：错误超过一定次数在缓存中添加修改锁
        String lockKey = Constants.Account.CODE_CHANGE_PASSWORD_LOCK + userRolesVo.getUid();
        //计数器Key：在缓存中记录错误次数
        String countKey = Constants.Account.CODE_CHANGE_PASSWORD_FAIL + userRolesVo.getUid();

        ChangeAccountVO resp = new ChangeAccountVO();

        //缓存中存在修改锁时设置对应数据并返回账户修改视图对象
        if (redisUtils.hasKey(lockKey)) {
            //获取修改锁失效时间
            long expire = redisUtils.getExpire(lockKey);
            Date now = new Date();
            long minute = expire / 60;
            long second = expire % 60;
            SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            //设置错误码
            resp.setCode(403);
            Date afterDate = new Date(now.getTime() + expire * 1000);
            String msg = "由于您多次修改密码失败，修改密码功能已锁定，请在" + minute + "分" + second + "秒后(" + formatter.format(afterDate) + ")再进行尝试！";
            //设置错误信息
            resp.setMsg(msg);
            return resp;
        }

        //根据用户uid查询用户密码
        QueryWrapper<UserInfo> userInfoQueryWrapper = new QueryWrapper<>();
        userInfoQueryWrapper.select("uuid", "password")
                .eq("uuid", userRolesVo.getUid());
        UserInfo userInfo = userInfoEntityService.getOne(userInfoQueryWrapper, false);
        //与当前登录用户的密码进行比较判断，相同时执行修改密码操作
        if (userInfo.getPassword().equals(SecureUtil.md5(oldPassword))) {
            UpdateWrapper<UserInfo> updateWrapper = new UpdateWrapper<>();
            //md5加密用户密码
            updateWrapper.set("password", SecureUtil.md5(newPassword))
                    .eq("uuid", userRolesVo.getUid());
            boolean isOk = userInfoEntityService.update(updateWrapper);
            if (isOk) {
                //修改成功后设置成功状态码和信息
                resp.setCode(200);
                resp.setMsg("修改密码成功！您将于5秒钟后退出进行重新登录操作！");
                //清空缓存中的计数器
                redisUtils.del(countKey);
                //返回修改成功后的视图对象
                return resp;
            }
            throw new StatusSystemErrorException("系统错误：修改密码失败！");
        }
        //不同时对错误次数进行记录，达到5次时需30分钟后才可重试
        Integer count = (Integer) redisUtils.get(countKey);
        if (count == null) {
            //初始化修改错误计数器，30分钟不尝试，该限制会自动清空消失
            redisUtils.set(countKey, 1, 60 * 30);
            count = 0;
        } else if (count < 5) {
            redisUtils.incr(countKey, 1);
        }
        count++;
        if (count == 5) {
            //清空缓存中的计数器
            redisUtils.del(countKey);
            //设置30分钟的修改锁
            redisUtils.set(lockKey, "lock", 60 * 30);
        }
        //设置错误码和错误信息
        resp.setCode(400);
        resp.setMsg("原始密码错误！您已累计修改密码失败" + count + "次...");
        return resp;
    }

    /**
     * 方法名: changeEmail
     * 描述: 修改邮箱
     *
     * @param changeEmailDto
     * @return web.suzy.oj.pojo.vo.ChangeAccountVO
     * @date 2022/11/15 11:37
     * @auther YangSuzy
     **/
    public ChangeAccountVO changeEmail(ChangeEmailDTO changeEmailDto) throws StatusSystemErrorException, StatusFailException {
        //获取前端输入的数据
        String password = changeEmailDto.getPassword();
        String newEmail = changeEmailDto.getNewEmail();
        //数据可用性判断
        if (StringUtils.isEmpty(password) || StringUtils.isEmpty(newEmail)) {
            throw new StatusFailException("错误：密码或新邮箱不能为空！");
        }
        if (!Validator.isEmail(newEmail)) {
            throw new StatusFailException("邮箱格式错误！");
        }
        //获取当前登录的用户
        AccountProfile userRolesVo = (AccountProfile) SecurityUtils.getSubject().getPrincipal();

        //修改锁Key：错误超过一定次数在缓存中添加修改锁
        String lockKey = Constants.Account.CODE_CHANGE_EMAIL_LOCK + userRolesVo.getUid();
        //计数器Key：在缓存中记录错误次数
        String countKey = Constants.Account.CODE_CHANGE_EMAIL_FAIL + userRolesVo.getUid();

        ChangeAccountVO resp = new ChangeAccountVO();

        //缓存中存在修改锁时设置对应数据并返回账户修改视图对象
        if (redisUtils.hasKey(lockKey)) {
            //获取修改锁失效时间
            long expire = redisUtils.getExpire(lockKey);
            Date now = new Date();
            long minute = expire / 60;
            long second = expire % 60;
            SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            //设置错误码
            resp.setCode(403);
            Date afterDate = new Date(now.getTime() + expire * 1000);
            String msg = "由于您多次修改邮箱失败，修改邮箱功能已锁定，请在" + minute + "分" + second + "秒后(" + formatter.format(afterDate) + ")再进行尝试！";
            //设置错误信息
            resp.setMsg(msg);
            return resp;
        }

        //根据用户uid查询用户密码
        QueryWrapper<UserInfo> userInfoQueryWrapper = new QueryWrapper<>();
        userInfoQueryWrapper.select("uuid", "password")
                .eq("uuid", userRolesVo.getUid());
        UserInfo userInfo = userInfoEntityService.getOne(userInfoQueryWrapper, false);

        //与当前登录用户的密码进行比较判断，相同时执行修改邮箱操作
        if (userInfo.getPassword().equals(SecureUtil.md5(password))) {
            UpdateWrapper<UserInfo> updateWrapper = new UpdateWrapper<>();
            updateWrapper.set("email", newEmail)
                    .eq("uuid", userRolesVo.getUid());

            boolean isOk = userInfoEntityService.update(updateWrapper);
            if (isOk) {
                //获取用户角色信息并转换为用户信息视图对象
                SysUserRoleVO userRoles = userRoleEntityService.getUserRoles(userRolesVo.getUid(), null);
                UserInfoVO userInfoVo = new UserInfoVO();
                BeanUtil.copyProperties(userRoles, userInfoVo, "roles");
                userInfoVo.setRoleList(userRoles
                        .getRoles()
                        .stream()
                        .map(SysRole::getRole)
                        .collect(Collectors.toList()));
                //修改成功后设置成功状态码和信息
                resp.setCode(200);
                resp.setMsg("修改邮箱成功！");
                resp.setUserInfo(userInfoVo);
                //清空缓存中的计数器
                redisUtils.del(countKey);
                //返回修改成功后的视图对象
                return resp;
            }
            throw new StatusSystemErrorException("系统错误：修改邮箱失败！");
        }
        //不同时对错误次数进行记录，达到5次时需30分钟后才可重试
        Integer count = (Integer) redisUtils.get(countKey);
        if (count == null) {
            //初始化修改错误计数器，30分钟不尝试，该限制会自动清空消失
            redisUtils.set(countKey, 1, 60 * 30);
            count = 0;
        } else if (count < 5) {
            redisUtils.incr(countKey, 1);
        }
        count++;
        if (count == 5) {
            //清空缓存中的计数器
            redisUtils.del(countKey);
            //设置30分钟的修改锁
            redisUtils.set(lockKey, "lock", 60 * 30);
        }
        //设置错误码和错误信息
        resp.setCode(400);
        resp.setMsg("密码错误！您已累计修改邮箱失败" + count + "次...");
        return resp;
    }


    /**
     * 方法名: changeUserInfo
     * 描述: 修改用户信息
     *
     * @param userInfoVo
     * @return web.suzy.oj.pojo.vo.UserInfoVO
     * @date 2022/11/15 12:01
     * @auther YangSuzy
     **/
    public UserInfoVO changeUserInfo(UserInfoVO userInfoVo) throws StatusFailException {
        //获取前端输入的数据
        String realname = userInfoVo.getRealname();
        String nickname = userInfoVo.getNickname();
        //数据可用性判断
        if (!StringUtils.isEmpty(realname) && realname.length() > 50) {
            throw new StatusFailException("真实姓名的长度不能超过50位");
        }
        if (!StringUtils.isEmpty(nickname) && nickname.length() > 20) {
            throw new StatusFailException("昵称的长度不能超过20位");
        }
        commonValidator.validateContent(userInfoVo.getSignature(), "个性简介");
        commonValidator.validateContent(userInfoVo.getNumber(), "学号", 200);
        commonValidator.validateContent(userInfoVo.getGrade(), "年级", 100);
        commonValidator.validateContent(userInfoVo.getCourse(), "专业", 100);
        commonValidator.validateContent(userInfoVo.getClasses(), "班级", 100);

        // 获取当前登录的用户
        AccountProfile userRolesVo = (AccountProfile) SecurityUtils.getSubject().getPrincipal();
        //更新用户信息
        UpdateWrapper<UserInfo> updateWrapper = new UpdateWrapper<>();
        updateWrapper.eq("uuid", userRolesVo.getUid())
                .set("realname", realname)
                .set("nickname", nickname)
                .set("signature", userInfoVo.getSignature())
                .set("grade", userInfoVo.getGrade())
                .set("course", userInfoVo.getCourse())
                .set("classes", userInfoVo.getClasses())
                .set("gender", userInfoVo.getGender())
                .set("number", userInfoVo.getNumber());
        boolean isOk = userInfoEntityService.update(updateWrapper);
        if (isOk) {
            //获取更新后的用户角色信息
            SysUserRoleVO userRoles = userRoleEntityService.getUserRoles(userRolesVo.getUid(), null);
            //更新session
            BeanUtil.copyProperties(userRoles, userRolesVo);
            UserInfoVO userInfoVO = new UserInfoVO();
            BeanUtil.copyProperties(userRoles, userInfoVO, "roles");
            userInfoVO.setRoleList(userRoles.getRoles().stream().map(SysRole::getRole).collect(Collectors.toList()));
            //返回更新后的用户信息
            return userInfoVO;
        }
        throw new StatusFailException("更新个人信息失败！");
    }

    /**
     * 方法名: getUserHomeInfo
     * 描述: 获取用户主页信息
     *
     * @param uid      用户ID
     * @param username 用户名
     * @return web.suzy.oj.pojo.vo.UserHomeVO
     * @date 2022/11/23 14:26
     * @auther YangSuzy
     **/
    public UserHomeVO getUserHomeInfo(String uid, String username) throws StatusFailException {
        AccountProfile userRolesVo = (AccountProfile) SecurityUtils.getSubject().getPrincipal();
        //如果没有传入uid和username，默认查询当前登录用户
        if (StringUtils.isEmpty(uid) && StringUtils.isEmpty(username)) {
            if (userRolesVo != null) {
                uid = userRolesVo.getUid();
            } else {
                throw new StatusFailException("请求参数错误：uid和username不能都为空！");
            }
        }
        //根据用户ID或用户名查询用户主页信息
        UserHomeVO userHomeInfo = userRecordEntityService.getUserHomeInfo(uid, username);
        if (userHomeInfo == null) {
            throw new StatusFailException("用户不存在");
        }
        //查询用户的通过题目列表
        QueryWrapper<UserAcproblem> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("uid", userHomeInfo.getUid())
                //过滤重复的题目ID
                .select("distinct pid", "submit_id")
                .orderByAsc("submit_id");
        List<UserAcproblem> acProblemList = userAcproblemEntityService.list(queryWrapper);
        List<Long> pidList = acProblemList.stream().map(UserAcproblem::getPid).collect(Collectors.toList());
        //存储题目的展示ID
        List<String> disPlayIdList = new LinkedList<>();
        if (pidList.size() > 0) {
            //查询通过题目的难度和展示ID
            QueryWrapper<Problem> problemQueryWrapper = new QueryWrapper<>();
            problemQueryWrapper.select("id", "problem_id", "difficulty");
            problemQueryWrapper.in("id", pidList);
            List<Problem> problems = problemEntityService.list(problemQueryWrapper);
            //将题目信息转换为用户主页的题目信息
            Map<Integer, List<UserHomeProblemVO>> map = problems.stream()
                    .map(this::convertProblemVO)
                    .collect(Collectors.groupingBy(UserHomeProblemVO::getDifficulty));
            userHomeInfo.setSolvedGroupByDifficulty(map);
            //保存题目的展示ID
            disPlayIdList = problems.stream().map(Problem::getProblemId).collect(Collectors.toList());
        }
        //设置已解决题目列表
        userHomeInfo.setSolvedList(disPlayIdList);
        //查询用户的最近一条登录记录
        QueryWrapper<Session> sessionQueryWrapper = new QueryWrapper<>();
        sessionQueryWrapper.eq("uid", userHomeInfo.getUid())
                .orderByDesc("create_time")
                .last("limit 1");
        //设置最近登录时间
        Session recentSession = sessionEntityService.getOne(sessionQueryWrapper, false);
        if (recentSession != null) {
            userHomeInfo.setRecentLoginTime(recentSession.getCreateTime());
        }
        return userHomeInfo;
    }

    /**
     * 方法名: convertProblemVO
     * 描述: 将题目信息转换为用户主页的题目信息
     *
     * @param problem 题目信息
     * @return web.suzy.oj.pojo.vo.UserHomeProblemVO
     * @date 2022/11/23 14:32
     * @auther YangSuzy
     **/
    private UserHomeProblemVO convertProblemVO(Problem problem) {
        return UserHomeProblemVO.builder()
                .problemId(problem.getProblemId())
                .id(problem.getId())
                .difficulty(problem.getDifficulty())
                .build();
    }

    /**
     * 方法名: getUserCalendarHeatmap
     * 描述: 获取用户主页的提交热力图
     *
     * @param uid      用户ID
     * @param username 用户名
     * @return web.suzy.oj.pojo.vo.UserCalendarHeatmapVO
     * @date 2022/11/23 15:59
     * @auther YangSuzy
     **/
    public UserCalendarHeatmapVO getUserCalendarHeatmap(String uid, String username) throws StatusFailException {
        AccountProfile userRolesVo = (AccountProfile) SecurityUtils.getSubject().getPrincipal();
        //如果没有传入uid和username，默认查询当前登录用户
        if (StringUtils.isEmpty(uid) && StringUtils.isEmpty(username)) {
            if (userRolesVo != null) {
                uid = userRolesVo.getUid();
            } else {
                throw new StatusFailException("请求参数错误：uid和username不能都为空！");
            }
        }
        //初始化热力图，设置最后日期为今天
        UserCalendarHeatmapVO userCalendarHeatmapVo = new UserCalendarHeatmapVO();
        userCalendarHeatmapVo.setEndDate(DateUtil.format(new Date(), "yyyy-MM-dd"));
        //获取用户最近一年的提交记录
        List<Judge> lastYearUserJudgeList = userRecordEntityService.getLastYearUserJudgeList(uid, username);
        if (CollectionUtils.isEmpty(lastYearUserJudgeList)) {
            userCalendarHeatmapVo.setDataList(new ArrayList<>());
            return userCalendarHeatmapVo;
        }
        //获取每个提交记录的提交时间
        HashMap<String, Integer> tmpRecordMap = new HashMap<>();
        for (Judge judge : lastYearUserJudgeList) {
            Date submitTime = judge.getSubmitTime();
            String dateStr = DateUtil.format(submitTime, "yyyy-MM-dd");
            //相当于map.put(dateStr, map.getOrDefault(dateStr, 0) + 1)
            //记录当天提交次数
            tmpRecordMap.merge(dateStr, 1, Integer::sum);
        }
        //设置热力图数据
        List<HashMap<String, Object>> dataList = new ArrayList<>();
        for (Map.Entry<String, Integer> record : tmpRecordMap.entrySet()) {
            HashMap<String, Object> tmp = new HashMap<>(2);
            tmp.put("date", record.getKey());
            tmp.put("count", record.getValue());
            dataList.add(tmp);
        }
        userCalendarHeatmapVo.setDataList(dataList);
        return userCalendarHeatmapVo;
    }
}
